使用 HttpClient 呼叫 WebService
TLDR
- 在無法加入 Web 參考的環境下,可透過
HttpClient以 SOAP 訊息格式手動呼叫 WebService。 - 針對 .NET Core 或無法使用
System.Web.Services的環境,建議使用XmlSerializer進行物件與 XML 的序列化與反序列化,以處理複雜型別。 - 透過
HttpClient.PostAsync發送符合 SOAP 1.2 規範的 XML 內容,並解析回傳的XDocument,即可實現動態呼叫。
解決無法加入 Web 參考的問題
什麼情況下會遇到這個問題:當開發環境因網路限制或架構限制,無法直接透過 Visual Studio 加入 Web 參考(WSDL)時。
在 .NET Framework 環境中,傳統做法是利用 WebClient 讀取 WSDL,並透過 ServiceDescriptionImporter 與 Reflection 動態編譯產生服務程式碼。但在 .NET Core 之後,由於缺乏 System.Web.Services 函式庫,此方法不再適用。此時,最穩定的替代方案是將 WebService 視為一個 HTTP 端點,直接透過 HttpClient 發送符合 SOAP 規範的 XML 請求。
使用 HttpClient 實作 SOAP 呼叫
什麼情況下會遇到這個問題:當需要呼叫 WebService,但無法使用自動產生的 Proxy 類別,且傳輸資料包含複雜物件時。
為了處理複雜的物件型別,我們可以使用 XmlSerializer 將 C# 物件轉換為 XML 格式,並封裝在 SOAP Envelope 中。以下是實作工具類別的建議做法:
csharp
public static class WebServiceUtils {
private static readonly HttpClient httpClient = new HttpClient();
public static async Task<TResponse> ExecuteAsync<TResponse>(string uri, string method, IDictionary<string, string> arguments, string @namespace = "http://tempuri.org/") {
XmlSerializerNamespaces serializerNamespaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
XmlWriterSettings settings = new XmlWriterSettings {
Indent = true,
OmitXmlDeclaration = true
};
string argsXml = string.Join("", arguments.Select(x => {
Type type = x.Value.GetType();
XmlSerializer _serializer = new XmlSerializer(type);
StringBuilder sb = new StringBuilder();
using (XmlWriter writer = XmlWriter.Create(sb, settings)) {
_serializer.Serialize(writer, x.Value, serializerNamespaces);
// 使用 Regex 確保 XML 標籤名稱符合 WebService 預期的參數名稱
return Regex.Replace(sb.ToString(), $@"((?<=^<)(\w*)(?=>))|(?<=</)\w*(?=>$)", x.Key);
}
}));
string soapXml = $@"
<soap12:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap12=""http://www.w3.org/2003/05/soap-envelope"">
<soap12:Body>
<{method} xmlns=""{@namespace}"">
{argsXml}
</{method}>
</soap12:Body>
</soap12:Envelope>
";
StringContent content = new StringContent(soapXml, Encoding.UTF8, "text/xml");
using (HttpResponseMessage message = await httpClient.PostAsync(uri, content).ConfigureAwait(false)) {
if (!message.IsSuccessStatusCode) {
throw new HttpRequestException($"HTTP request failed with status code {message.StatusCode}: {message.ReasonPhrase}");
}
string result = await message.Content.ReadAsStringAsync().ConfigureAwait(false);
XDocument xdoc = XDocument.Parse(result);
XNamespace ns = @namespace;
string resultTag = method + "Result";
XElement xelement = xdoc.Descendants(ns + resultTag).Single();
XmlSerializer serializer = new XmlSerializer(typeof(TResponse), new XmlRootAttribute(resultTag) { Namespace = @namespace });
using (XmlReader reader = xelement.CreateReader()) {
return (TResponse)serializer.Deserialize(reader);
}
}
}
}驗證結果
透過上述方法,即使是包含巢狀結構的 Request 與 Response 物件,也能正確地被序列化並傳遞給 WebService。在監看式中可以確認:
- 請求參數已正確對應至 WebService 方法的參數名稱。
- 回傳的 XML 已成功反序列化為指定的
TResponse型別。
 
異動歷程
- 2023-02-13 初版文件建立。
